package xin.pms.service;


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import xin.basic.model.Dict;
import xin.basic.repository.DictRepository;
import xin.common.http.response.PageResponse;
import xin.common.http.response.PageWrapper;
import xin.common.utils.AssertUtils;
import xin.core.jpa.JpaConstant;
import xin.pms.enums.MenuType;
import xin.pms.enums.OptPermission;
import xin.pms.enums.PermissionType;
import xin.pms.model.Menu;
import xin.pms.model.MenuNode;
import xin.pms.model.Permission;
import xin.pms.model.User;
import xin.pms.repository.MenuRepository;
import xin.pms.repository.PermissionRepository;
import xin.pms.repository.UserRepository;
import xin.pms.request.MenuPageRequest;
import xin.pms.request.MenuRequest;

import javax.persistence.criteria.Predicate;
import java.util.*;

/**
 * <pre>
 * Menu Service
 * </pre>
 *
 * @author lixin_ma@outlook.com
 * @version 1.0
 * @since 2018/7/28 15:39
 */
@Service
@Slf4j
@Transactional(readOnly = true)
public class MenuService {

    @Autowired
    private MenuRepository repository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PermissionRepository permissionRepository;

    @Autowired
    private DictRepository dictRepository;

    /**
     * get menu list by user io and menu parent io
     * @param userId user io
     * @param menuPid menu parent io
     * @return
     */
    public List<Menu> getMenuByUserIdAndMenuParentId(String userId, String menuPid) {
        List<Menu> menus = repository.findByParentIdOrderByPriority(menuPid);
        List<Menu> tar = new ArrayList<>();
        List<Permission> allMenuPermissions = permissionRepository.findByPermissionType(PermissionType.menu);

        User currentUser = userRepository.findOne(userId);
        Set<Permission> userPermissions = currentUser.getAllPermissions();//get current user permission list

        menus.forEach(menu -> {
            Permission menuPermission = permissionRepository.findOne(menu.getPermissionCode());//get menu permission
            if (!menu.getDeleted() && menu.getVisible() && (StringUtils.isBlank(menu.getPermissionCode())
                    || userPermissions.contains(menuPermission))) {
                checkMenuPermission(allMenuPermissions, menu.getChildren());
                tar.add(menu);
            }
        });

        /**
         * sort
         */
        tar.sort(Comparator.comparing(Menu::getPriority));
        tar.forEach(menu -> menu.getChildren().sort(Comparator.comparing(Menu::getPriority)));
        return tar;
    }

    /**
     * check menu permission
     * @param permissions full permission list
     * @param menus menu list
     */
    private void checkMenuPermission(Collection<Permission> permissions, List<Menu> menus) {
        for (Iterator<Menu> menuIterator = menus.iterator(); menuIterator.hasNext();) {
            Menu menu = menuIterator.next();
            Permission permission = permissionRepository.findOne(menu.getPermissionCode());
            if ((null == menu.getPermissionCode() || "".equals(menu.getPermissionCode().trim())
                    || ( permission != null && permissions.contains(permission))
            ) && !menu.getDeleted() && menu.getVisible()) {
                checkMenuPermission(permissions,menu.getChildren());
            } else {
                menuIterator.remove();
            }
        }
    }

    /**
     * add menu
     * 1: move pre node(priority + 1 )
     * 2: set parent node and pre node
     * @param node menu node
     * @return
     */
    @Transactional
    public Menu save(MenuNode node) {
        int offset = 1;
        if(AssertUtils.isNotNull(node.getOptPermissions()))
            offset += node.getOptPermissions().size();
        Menu pre = repository.findOne(node.getPreId());
        int priority  = pre.getPriority() + 1;
        List<Permission> afterPermissions = permissionRepository.findByPriorityGreaterThan(pre.getPriority());
        if(AssertUtils.isNotNull(afterPermissions)){
            for(Permission item : afterPermissions){
                item.setPriority(item.getPriority() + offset);
                item.setLastModifyTime(new Date(System.currentTimeMillis()));
                permissionRepository.save(item);

            }
        }

        Permission permission = new Permission();
        permission.setPermissionType(PermissionType.menu);
        permission.setCode(node.getPermissionCode());
        permission.setName(node.getName());
        permission.setDescription(node.getName());
        permission.setPriority(priority);
        permission.setCreateTime(new Date(System.currentTimeMillis()));
        permission.setLastModifyTime(new Date(System.currentTimeMillis()));

        permissionRepository.save(permission);

        if(AssertUtils.isNotNull(node.getOptPermissions())){
            for(int index = 0 ; index < node.getOptPermissions().size() ; index++){
                Permission temp = new Permission();
                temp.setPermissionType(PermissionType.btn);
                temp.setCode(node.getPermissionCode() +  "." + node.getOptPermissions().get(index));
                temp.setPriority(priority + index + 1);
                temp.setCreateTime(new Date(System.currentTimeMillis()));
                temp.setLastModifyTime(new Date(System.currentTimeMillis()));
                permissionRepository.save(temp);
            }
        }

        List<Menu> afterMenus = repository.findByPriorityGreaterThan(pre.getPriority());

        if(afterMenus != null && !afterMenus.isEmpty()){
            for(Menu item : afterMenus){
                item.setPriority(item.getPriority() + offset);
                item.setLastModifyTime(new Date(System.currentTimeMillis()));
                repository.save(item);
            }
        }

        Menu menu = new Menu();
        menu.saveInit(node,node.getParentId(),priority);
        if(AssertUtils.isNotNull(node.getOptPermissions())){
            List<Menu> children = new ArrayList<>();
            for(int index= 0 ;index<node.getOptPermissions().size() ;index++){
                Menu tempMenu = new Menu();
                tempMenu.setPermissionCode(node.getPermissionCode() +  "." + node.getOptPermissions().get(index));
                tempMenu.setType(MenuType.BTN);
                tempMenu.setCreateTime(new Date(System.currentTimeMillis()));
                tempMenu.setLastModifyTime(new Date(System.currentTimeMillis()));
                children.add(tempMenu);
            }
            menu.setChildren(children);
        }
        return repository.save(menu);
    }

    /**
     * update menu (only update menu info,priority not include )
     * @param node Menu node Model
     */
    @Transactional(readOnly = false)
    public Menu update(MenuNode node) {
        Menu menu = repository.findOne(node.getId());
        return repository.save(menu.updateInit(node));
    }

    /**
     * <pre>
     *  modify menu priority
     * 1、pre node priority -1 <br/>
     * 2、curr node priority +1 <br/>
     * 3、change parent node <br/>
     * 4、save <br/>
     * <pre/>
     * @param request
     */
    @Transactional(readOnly = false)
    public void modifyParent(MenuRequest request) {
        Menu menu = repository.findOne(request.getId());
        Menu pre = repository.findOne(request.getId());
        Integer priority = pre.getPriority() + 1;

        List<Menu> oldNexts = repository.findByPriorityGreaterThan(menu.getPriority());
        oldNexts.sort(Comparator.comparing(Menu::getPriority));
        if(oldNexts !=null &&!oldNexts.isEmpty()){
            oldNexts.forEach(item->{
                item.setPriority(item.getPriority() - 1);
                repository.save(item);
            });
        }

        List<Menu> pres =  repository.findByPriorityGreaterThan(pre.getPriority());
        pres.sort(Comparator.comparing(Menu::getPriority));
        if(pres !=null &&!pres.isEmpty()){
            pres.forEach(item->{
                item.setPriority(item.getPriority() + 1);
                repository.save(item);
            });
        }

        menu.setPriority(priority);
        menu.setParentId(request.getParent());
        menu.setLastModifyTime(new Date(System.currentTimeMillis()));
        repository.save(menu);
    }
    /**
     * logic del
     * @param menuId
     */
    @Transactional(readOnly = false)
    public void logicalDel(String menuId) {
        Menu menu = repository.findOne(menuId);
        menu.setDeleted(Boolean.TRUE);
        menu.setLastModifyTime(new Date(System.currentTimeMillis()));
        repository.save(menu);


        /**
         * TODO unbind all thing about this menu
         */
    }

    public List<Menu> getByParent(String parentId) {
        List<Menu> menus = repository.findByParentId(parentId);
        List<Menu> tar = new ArrayList<>();
        for (Iterator<Menu> menuIterator = menus.iterator(); menuIterator.hasNext();) {
            Menu menu = menuIterator.next();
            if (!menu.getDeleted() && menu.getVisible()) {
                checkMenu(menu.getChildren());
                tar.add(menu);
            }
        }
        tar.sort(Comparator.comparing(Menu::getPriority));
        tar.forEach(menu -> menu.getChildren().sort(Comparator.comparing(Menu::getPriority)));
        return tar;
    }

    private void checkMenu(List<Menu> menus) {
        for (Iterator<Menu> menuIterator = menus.iterator(); menuIterator.hasNext();) {
            Menu menu = menuIterator.next();
            if (!menu.getDeleted() && menu.getVisible()) {
                checkMenu(menu.getChildren());
            } else {
                menuIterator.remove();
            }
        }
    }

    public Menu findOne(String menuId) {
        return repository.findOne(menuId);
    }

    public PageResponse findPageList(MenuPageRequest request) {
        Sort sort = new Sort(Sort.Direction.DESC,"lastModifyTime");
        Pageable pageable = new PageRequest(request.getPageNumber()-1,request.getPageSize(),sort);
        Specification<Menu> specification = (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            if(xin.common.utils.StringUtils.isNotBlank(request.getName())){
                predicates.add(criteriaBuilder.like(root.get("name"),
                        JpaConstant.WILDCARD + request.getName() + JpaConstant.WILDCARD));
            }
            if(xin.common.utils.StringUtils.isNotBlank(request.getParentId())){
                predicates.add(criteriaBuilder.equal(root.get("parentId"),request.getParentId()));
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
        };
        return PageWrapper.wrap(repository.findAll(specification, pageable));
    }
}
